home *** CD-ROM | disk | FTP | other *** search
- /*
- * play.c - Plays module
- *
- * (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
- */
-
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <string.h>
- #include <fcntl.h>
- #include <sys/ioctl.h>
- #include <sys/soundcard.h>
- #include <sys/ultrasound.h>
- #include <limits.h>
- #include <sys/time.h>
-
- #include "mod.h"
- #include "message.h"
-
- /* Sample #0 is used as a "silent sample" that will cause the active voice
- * to be silenced.
- */
-
- SEQ_DECLAREBUF();
- extern int seqfd, gus_dev;
- extern struct mod_info M;
- extern struct options opt;
-
- /* Variables used to play module */
-
- struct voice V[MAX_VOICES];
- struct effects efx;
- struct event *cur_event;
-
- int songpos, linepos, tick, speed, tempo;
- double mod_time, tick_time;
- char is_first_ult_effect;
-
- char song_end, restart_song, advance_songpos;
-
- /* Used to make sure we notify main of speed/tempp-changes */
- static int last_speed, last_tempo;
-
- /* Tables */
- extern int periodtable[NR_OCTAVES*12];
-
- void play_tick(void)
- {
- int i;
-
- if(_seqbufptr)
- error("Buffer not flushed on entry to play_tick()\n");
-
- tick++;
- if(tick >= speed) { /* Next line/position ... */
- tick=0;
-
- if(efx.pattern_delay) /* Update patterndelay if needed */
- --efx.pattern_delay;
-
- /* Check line jumps (EFX_LOOP) on tick #0 in each line */
-
- if(efx.PBreakFlag) {
- linepos=efx.PBreakPos;
- efx.PBreakPos=0;
- efx.PBreakFlag=0;
- advance_songpos=0;
- }
-
- /* Check patternjumps (EFX_BREAK|JMP) on tick #0 in each line. */
-
- /* NOTE: A line with EFX_LOOP and then EFX_BREAK on a voice with
- * higher number will change pattern and start on line 0 regardless
- * of the argument to EFX_BREAK. This seems like a bug, but
- * as this is the behaviour of ProTracker, we will emulate it.
- */
-
- if(efx.PosJumpFlag) {
- linepos=efx.PBreakPos; /* Set line to continue at */
- advance_songpos=1;
- }
- else {
- if(!efx.pattern_delay) {
- if(++linepos == 64) { /* Just advance normally */
- linepos=0;
- advance_songpos=1;
- }
- }
- }
-
- if(advance_songpos) {
- songpos++;
- if(M.format == MODFORMAT_MOD || M.format == MODFORMAT_MTM)
- songpos=(songpos&0x7f); /* PT/MTM allows 0x80 songpos's */
- if(M.format == MODFORMAT_ULT)
- songpos=(songpos&0xff); /* ULT allows 0x100 songpos's */
- if(M.format == MODFORMAT_S3M) {
- while(songpos < M.songlength) {
- if(M.patterntable[songpos] == S3M_ORDER_SKIP) {
- songpos++;
- continue;
- }
- if(M.patterntable[songpos] == S3M_ORDER_SONGEND)
- songpos=M.songlength;
- break;
- }
- }
-
- /* End song if it's done or an invalid pattern # is found */
- if(songpos >= M.songlength ||
- M.patterntable[songpos] > M.nr_patterns) {
- restart_song=1;
- if(opt.loop_module || (!opt.break_loops && M.restartpos))
- songpos=M.restartpos;
- else
- song_end=1;
- }
- efx.PBreakPos=0;
- efx.PosJumpFlag=0;
- restart_song=0; /* Either set above or in EFX_JUMP */
- }
- }
-
- if(song_end) {
- for(i=0; i < M.nr_voices; ++i)
- MY_SEQ_STOP_NOTE(gus_dev, i, V[i].note, 0);
-
- SEQ_WAIT_TIME((int)mod_time+10); /* Wait 100 extra milli-seconds */
- SEQ_WAIT_TIME((int)mod_time+11); /* Dummy to make sure we don't exit
- * until above events has taken effect
- */
- SEQ_ECHO_BACK(MESSAGE_DONE);
- return; /* No printing or processing when song is done */
- }
-
- SEQ_WAIT_TIME((int)(mod_time+0.5));
- i=_seqbufptr;
-
- if(!tick && !efx.pattern_delay) {
- if(advance_songpos)
- advance_songpos=0;
-
- /* ALWAYS send speed _before_ line (catchup-code depends on this! */
- if(speed != last_speed || tempo != last_tempo) {
- send_speed();
- last_speed=speed;
- last_tempo=tempo;
- }
- send_line();
- }
-
- process_line(efx.pattern_delay ? 1 : 0);
-
- mod_time+=tick_time;
- if(_seqbufptr == i) /* Remove WAIT-event if possible */
- _seqbufptr=0;
- }
-
-
- /* When continue_effects is nonzero we just continue uppdating effects that
- * are updated when tick is nonzero (this is for EFX_PATTERNDELAY).
- */
-
- void process_line(int continue_effects)
- {
- int v;
- struct event tmp_event, *orig_event;
- int this_songpos=songpos; /* In case songpos gets changes in EFX_? */
-
- for(v=0; v < M.nr_voices; ++v) {
- efx.set_volume=0;
- efx.set_finepitch=0;
- efx.set_balance=0;
- efx.dont_trigger_note=0;
- efx.retrig_note=0;
- efx.kill_voice=0;
-
- cur_event=GET_EVENT_PTR(v, M.patterntable[this_songpos], linepos);
-
- if(!tick && !continue_effects) { /* Tick #0 */
-
- /* Sample change on the voice? */
- if(cur_event->sample) {
- if(cur_event->sample <= M.nr_samples &&
- M.sample[cur_event->sample].valid) {
- if(cur_event->sample != V[v].sample) {
- /* See TECH-file for a comment on changing samples
- */
- SEQ_SET_PATCH (gus_dev, v, cur_event->sample);
- V[v].sample=cur_event->sample;
- }
-
- /* Try to reduce number of calls to do_set_finepitch() */
- if(V[v].finetune != M.sample[cur_event->sample].finetune
- || V[v].pitchbend)
- efx.set_finepitch=1;
-
- V[v].finetune=M.sample[cur_event->sample].finetune;
- V[v].sampleoffset=0;
- }
- else {
- /* Change to nonspecified sample -> silence the voice */
- efx.kill_voice=1;
- }
- }
- /* Reset volume to default instrument-volume if we have
- * a (valid) sample specified. Also make sure it affects
- * a sample that is already playing.
- */
- if(cur_event->sample && M.sample[cur_event->sample].valid) {
- V[v].volume=V[v].real_volume=M.sample[V[v].sample].volume;
- efx.set_volume=1;
- }
-
- /* Second effect is used for "volume"-field in S3M's */
- if(M.format == MODFORMAT_S3M && cur_event->effect2 == EFX_VOLUME) {
- V[v].volume=V[v].real_volume=cur_event->arg2;
- efx.set_volume=1;
- }
-
- is_first_ult_effect=1;
- check_tick0_efx(v); /* Check effects */
-
- /* Check second effect if its a .ULT-file.
- * This is 'hack' but it does the job w/o requiring modifications
- * to effects.c.
- */
- if(M.format == MODFORMAT_ULT) {
- is_first_ult_effect=0;
- tmp_event=*cur_event;
- orig_event=cur_event;
- cur_event=&tmp_event;
- cur_event->effect=cur_event->effect2;
- cur_event->arg=cur_event->arg2;
-
- check_tick0_efx(v);
- cur_event=orig_event;
- }
-
- /* Silence voice if needed */
- if(efx.kill_voice) {
- V[v].sample=0;
- V[v].volume=V[v].real_volume=0;
- MY_SEQ_START_NOTE(gus_dev, v, 255, 0);
- }
-
- /* Set balance if needed */
- if(efx.set_balance)
- SEQ_PANNING(gus_dev, v, ((int)M.panning[v])*17-128);
-
- /* If we have a note and no EFX_PORTANOTE(&VS) or EFX_NOTEDELAY,
- * we trigger it (unless it's a NOTE_OFF ofcourse)
- */
- if(cur_event->note && cur_event->note != NOTE_OFF &&
- !efx.dont_trigger_note) {
- if(V[v].sample) {
- MY_SEQ_START_NOTE(gus_dev, v, cur_event->note,
- convert_volume(V[v].real_volume));
- if(V[v].sampleoffset)
- GUS_VOICE_POS(gus_dev, v, V[v].sampleoffset);
- if(efx.set_finepitch)
- do_set_finepitch(v);
- }
- }
- else { /* No note to play, change volume/finepitch if needed
- * (and there is a sample specified for the voice).
- */
- if(cur_event->note == NOTE_OFF)
- MY_SEQ_STOP_NOTE(gus_dev, v, 0, 0);
-
- if(V[v].sample) {
- if(efx.set_volume)
- MY_SEQ_START_NOTE(gus_dev, v, 255,
- convert_volume(V[v].real_volume));
- if(efx.set_finepitch)
- do_set_finepitch(v);
- }
- }
- }
- else { /* NOT tick #0 */
- is_first_ult_effect=1;
- check_efx(v); /* Update effects */
-
- /* Update second effect if its a .ULT-file.
- */
- if(M.format == MODFORMAT_ULT) {
- is_first_ult_effect=0;
- tmp_event=*cur_event;
- orig_event=cur_event;
- cur_event=&tmp_event;
- cur_event->effect=cur_event->effect2;
- cur_event->arg=cur_event->arg2;
-
- check_efx(v);
- cur_event=orig_event;
- }
-
- if(V[v].sample) {
- if((efx.retrig_note && cur_event->note != NOTE_OFF) ||
- efx.set_volume)
- MY_SEQ_START_NOTE(gus_dev, v,
- (efx.retrig_note ? cur_event->note : 255),
- convert_volume(V[v].real_volume));
-
- if((efx.retrig_note && cur_event->note != NOTE_OFF) &&
- V[v].sampleoffset)
- GUS_VOICE_POS(gus_dev, v, V[v].sampleoffset);
-
- /* Here we always set finepitch if we have efx.retrig_note
- * as the efx.set_finepitch that is set at tick #0 is already
- * cleared, and there could be a finetunedifference.
- */
- if(efx.retrig_note || efx.set_finepitch)
- do_set_finepitch(v);
- }
- }
- }
- }
-
-
- void total_reset(int spd, int tmpo, int pos)
- {
- init_sequencer_voices(); /* Must be done first */
- init_voices();
-
- last_speed=last_tempo=-1;
- speed=spd;
- tempo=tmpo;
- tick_time=250.0/tempo;
-
- SEQ_START_TIMER();
- mod_time=0;
- SEQ_WAIT_TIME(0);
- SEQ_DUMPBUF();
- tick=speed;
-
- set_modulepos(pos);
- }
-
-
- void init_sequencer_voices(void)
- {
- int i;
-
- ioctl(seqfd, SNDCTL_SEQ_RESET, 0); /* Kill events */
- i=750;
- ioctl(seqfd, SNDCTL_SEQ_TRESHOLD, &i); /* Set threshold */
- _seqbufptr=0;
- GUS_NUMVOICES(gus_dev, M.nr_voices);
- SEQ_VOLUME_MODE(gus_dev, VOL_METHOD_LINEAR);
-
- for(i=0; i < M.nr_voices; ++i) {
- SEQ_PANNING(gus_dev, i, ((int)M.panning[i])*17-128);
- SEQ_BENDER_RANGE(gus_dev, i, 8191);
- SEQ_PITCHBEND(gus_dev, i, 0);
- }
- }
-
-
- void init_voices(void)
- {
- int i;
-
- for(i=0; i < M.nr_voices; ++i) {
- /* Clear voice-info */
- bzero((void*)&V[i], sizeof(struct voice));
-
- /* Waveforms are retriggered sines by default */
- V[i].vibrato_waveform=WAVEFORM_SINE;
- V[i].vibrato_retrig=1;
- V[i].tremolo_waveform=WAVEFORM_SINE;
- V[i].tremolo_retrig=1;
- V[i].loopstartpos=-1;
-
- /* Set note and period to valid values so pitchbender won't screw up */
- V[i].note=BASE_NOTE+4*12;
- V[i].period=V[i].real_period=periodtable[V[i].note-BASE_NOTE];
- }
- }
-
-
- void set_modulepos(int pos)
- {
- efx.pattern_delay=0;
-
- efx.PosJumpFlag=0;
- efx.PBreakFlag=0;
- efx.PBreakPos=0;
-
- songpos=pos-1;
- linepos=63;
- song_end=0;
- restart_song=0;
- }
-
-
- /* Converts 'v' to a volumelevel suitable for the sounddriver */
-
- int convert_volume(int v)
- {
- return MIN((v*128)/M.volrange, 127);
- }
-
-
- /* Sets the pitch for a voice. Should actually be in effects.c, but I wanted
- * to keep it clean from sequencer-commands.
- */
-
- void do_set_finepitch(int v)
- {
- set_pitch(v);
-
- SEQ_PITCHBEND(gus_dev, v, V[v].pitchbend+(V[v].finetune*100)/8);
- #if 0
- if(opt.active_voices&(1<<v) && opt.verbose >=5)
- printf("'%d+%d",V[v].pitchbend, V[v].finetune);
- #endif
- }
-